
            <!DOCTYPE html>
            <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>k8s 集群 Ingress Nginx传递用户真实IP问题</title>
            </head>
            <body>
            <a href="https://andyoung.blog.csdn.net">原作者博客</a>
            <div id="content_views" class="markdown_views prism-atom-one-light">
                    <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                        <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                    </svg>
                    <p>业务应用经常有需要用到用户真实ip的场景，比如：异地登录的风险预警、访问用户分布统计等功能等。当有这种需求的时候，在业务上容器过程中，如果用到ingress就要注意配置了。通常，用户ip的传递依靠的是X-Forwarded-*参数。但是默认情况下，ingress是没有开启的。</p> 
<p>Ingress 文档 <a href="https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration" rel="nofollow">https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration</a> 还比较详细</p> 
<p>修改 <code>Config Map</code>(ingress-nginx-controller)添加下发参数：</p> 
<pre><code>"compute-full-forwarded-for": "true",
"use-forwarded-headers": "true"
</code></pre> 
<p>重启之后还是不能获取到用户IP，是因为还需要需要改services 的 <code>externalTrafficPolicy: Local</code>。externalTrafficPolicy介绍：<a href="https://andyoung.blog.csdn.net/article/details/122215040" rel="nofollow">K8s中的external-traffic-policy是什么？</a></p> 
<p>原因是 <code>externalTrafficPolicy</code> 默认为<code>Cluster</code>。Kube-proxy在做转发的时候，会做一次SNAT (source network address translation)，所以源IP变成了转发源节点的IP地址。而<code>externalTrafficPolicy</code> 默认为<code>Local</code>。Kube-proxy转发时会保留源IP，但只转发给本机的容器，绝不跨节点转发。</p> 
<p>所以需要修改<code>deployment</code>,添加 node 亲和性。将 pod <code>ingress-nginx-controller</code> 调度到 ip所指向的node上，比如<code>a1</code>。</p> 
<pre><code>nodeSelector:
	kubernetes.io/os: linux
nodeName: a1
</code></pre> 
<p>在修改Services<code>ingress-nginx-controller</code></p> 
<pre><code>  type: NodePort
  externalTrafficPolicy: Local
</code></pre> 
<p>重启pod，查看ip，成功，happy。</p> 
<h3><a id="2_34"></a>下面介绍一下用到的2个参数</h3> 
<blockquote> 
 <p>注：在文档顶栏的搜索框搜索forward字样就可以找到这3个参数</p> 
</blockquote> 
<h4><a id="1_useforwardedheaders_38"></a>1. use-forwarded-headers</h4> 
<p>如果Nginx在其他7层代理或负载均衡后面，当期望Nginx将X-Forwarded-*的头信息传递给后端服务时，则需要将此参数设为true如果设为false（默认为false），Nginx会忽略掉X-Forwarded-*的头信息。false设置适用于Nginx直接对外或前面只有3层负载均衡的场景由于ingress的主配置是从configmap中获取的，更新参数则需要修改名为nginx-configuration的configmap的配置：在data配置块下添加use-forwarded-headers: “true”</p> 
<p>修改后，ingress nginx会自动加载更新nginx.conf主配置文件。下图为更新前后配置文件变化对比：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/78ee7e8633183fe1c42eed67f840f259.png" alt="img"></p> 
<p>注：左边为开启use-forwarded-headers后ingress nginx主配置文件，右边为开启前</p> 
<h4><a id="2_forwardedforheader_48"></a>2. forwarded-for-header</h4> 
<p>用来设置识别客户端来源真实ip的字段，默认是X-Forwarded-For。如果想修改为自定义的字段名，则可以在configmap的data配置块下添加：forwarded-for-header: “THE_NAME_YOU_WANT”。通常情况下，我们使用默认的字段名就满足需求，所以不用对这个字段进行额外配置。</p> 
<h4><a id="3_computefullforwardedfor_52"></a>3. compute-full-forwarded-for</h4> 
<p>如果只是开启了use-forwarded-headers: "true"的话，会发现还是没能获取到客户端来源的真实ip，原因是当前X-Forwarded-For变量是从remote_addr获取的值，每次取到的都是最近一层代理的ip。为了解决这个问题，就要配置compute-full-forwarded-for字段了，即在configmap的data配置块添加：compute-full-forwarded-for: “true”。其作用就是，将客户端用户访问所经过的代理ip按逗号连接的列表形式记录下来。</p> 
<p>待ingress nginx加载configmap并更新主配置文件后，对比更新前后变化如下：</p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/3ae500e83bbf6b4b690d24f69fe5aafe.png" alt="img"></p> 
<p><img src="https://i-blog.csdnimg.cn/blog_migrate/73d3a0d2d5638a18ca4f896ee4a119bc.png" alt="img"></p> 
<p>注：左边是未开启compute-full-forwarded-for配置的ingress nginx主配置文件，右边是开启了的</p> 
<h3><a id="_64"></a>举例说明</h3> 
<p>如果从客户端ip0发起一个HTTP请求到达服务器之前，经过了三个代理proxy1、proxy2、proxy3，对应的ip分别为ip1、ip2、ip3，那么服务端最后得到的X-Forwarded-For值为：ip0,ip1,ip2。列表中并没有ip3，ip3可以在服务端通过remote_addr来获得。这样应用程序通过获取X-Forwarded-For字段的第一个ip，就可以得到客户端用户真实ip了。</p> 
<h3><a id="_68"></a>注意项</h3> 
<p>值得注意的是，并不是所有的场景都能通过X-Forwarded-For来获取用户正式ip。比如，当服务器前端使用了CDN的时候，X-Forwarded-For方式获取到的可能就是CDN的来源ip了，这种情况，可以根CDN厂商约定一个字段名来记录用户真实ip，然后代理将这个字段逐层传递，最后到服务端。</p>
                </div>
            </body>
            </html>
            